CMU 15-112: Fundamentals of Programming and Computer Science
Day 13/14/15 Practice (Due never)



  1. 5 Reasoning About (Recursive) Code problems
    In just a few words of plain English, state what each of the following functions does in general. You will receive no credit for describing the line-by-line low-level behavior of the code. For example:
    def mystery(list): count = 0 for value in list: if (value == 0): count += 1 return count
    Correct answer: "This function returns the number of 0's in the given list." Or, if you prefer to be succinct, just: "number of 0's". Incorrect answer (no points): "This function sets count to 0, then sets value to each element in list in turn, and for each value, if it is 0, it adds one to count, and then returns count". This is all true, but completely misses the high-level behavior of the function, and so would receive zero points.

    1. def f(n): # assume n is a non-negative integer if (n < 10): return 1 else: return 1 + f(n//10)

    2. def f(a): # assume a is a list of strings if (len(a) == 0): return "" else: x = f(a[1:]) if (len(x) > len(a[0])): return x else: return a[0]

    3. def f(a): # assume a is a list of integers if (len(a) == 0): return 0 elif (len(a) == 1): return (a[0] % 2) else: i = len(a)//2 return f(a[:i]) + f(a[i:])

    4. def f(n): # assume n is a non-negative integer if (n == 0): return 1 else: return 2*f(n-1)

    5. def f(n): # assume n is a non-negative integer if (n == 0): return 0 else: return f(n-1) + 2*n - 1 # Hint: you may want to try this function with a few sample values. # The answer should quickly become apparent, though you may wish to # think about why the answer is in fact what it is.

  2. countFiles(path)
    Write the recursive function countFiles(path), which takes a string specifying a path to a folder and returns the total number of files in that folder (and all its subfolders). Do not count folders (or subfolders) as files. Do not worry if the supplied path is not valid or is not a folder. To help test your code, here are some assertions for use with sampleFiles:
    assert(countFiles("sampleFiles/folderB/folderF/folderG") == 0) assert(countFiles("sampleFiles/folderB/folderF") == 0) assert(countFiles("sampleFiles/folderB") == 2) assert(countFiles("sampleFiles/folderA/folderC") == 4) assert(countFiles("sampleFiles/folderA") == 6) assert(countFiles("sampleFiles") == 10)
    Note: regarding efficiency, it is overtly inefficient to create a list of any kind in your solution to this problem. In particular, you may not do this:
    def countFiles(path): return len(listFiles(path))

  3. permutations
    Write the recursive function permutations(a, k), which modifies the recursive permutations function from the course notes so that it returns all the permutations of length k from the elements in the list a, or the empty list [] if no such permutations exist. For example, permutations([1,2,3,4], 2) would return some ordering of this list:
    [[1, 2], [1, 3], [1, 4], [2, 1], [2, 3], [2, 4], [3, 1], [3, 2], [3, 4], [4, 1], [4, 2], [4, 3]]
    We say "some ordering" because your list must include the correct permutations, but in any order. And, once again, your solution must be recursive, and in fact it must be an obvious adaptation of the code in the course notes.

    For your testing purposes, it might be helpful to use Pythons built-in permutations function. For example, this might be helpful (it generates the solution above):
    import itertools a = [1,2,3,4] print [list(x) for x in itertools.permutations(a, 2)]
    Hint: do not worry about the case where the original list contains duplicate values.

    Note: regarding efficiency, your code should compute the 870-item result to permutations(range(30), 2) almost instantly, and the 117,600-item result to permutations(range(50),3) with at most a couple of seconds delay.

    Most solutions that are not efficient enough will run much too long on these examples. The reason: generally, one way or another, they will create lots of "extra" permutations and then slice, pop, or remove their way down to the required size. For example, this modification of the permutation code is not acceptable:
    def permutations(a, k): # inefficient version that uses permutations code from class, unmodified, # and then, assuming len(a)>=k, uses only first k elements of each permutation, # ignoring the rest (which is not ok). This also produces some duplicates, # so either have to remove all the duplicates at the end (which is also not ok), # or add an expensive O(n) check inside our loop to check if a permutation # is already in our list (which is also not ok). result = [ ] allPerms = allPermutations(a) for fullLengthPerm in allPerms: perm = fullLengthPerm[0:k] # not ok: essentially "removes" (n-k) elements if perm not in result: # also not ok result += [perm] print "len(allPerms) = ", len(allPerms) print "len(result) = ", len(result) return result def allPermutations(a): # renamed permutations code from class notes # returns a list of all permutations of the list a if (len(a) == 0): return [[]] else: allPerms = [ ] for subPermutation in allPermutations(a[1:]): for i in xrange(len(subPermutation)+1): allPerms += [subPermutation[:i] + [a[0]] + subPermutation[i:]] return allPerms
    Why is this unacceptable? Well, say we are finding permutations(range(9), 2). The answer contains 72 permutations of two numbers each. However, the code above will generate all 362,880 permutations of length 9 to find these 72 permutations. This huge amount of extra work makes it intractable to use this code to compute permutations(range(30),2), let alone permutations(range(50),3). Try it, if you want to wait a very, very long time!

    Big Hint: one way to solve this is to modify the permutations code from class to take a second parameter, k, and to return all the permutations of a that are no larger than length k. This still requires a non-recursive wrapper function (like above) that drops all the permutations that are not of length k, but since we never produce any permutations larger than length k, this is much, much faster than the code above.

  4. fractalFlower
    First, write the function simpleFlower that takes an integer number representing the size of the flower (and whatever else you need to do turtle graphics -- see the Koch Snowflake example for details). Your function will draw the following figure:

    The position and orientation of the flower will depend on the initial position and orientation of the turtle. The origin of the flower is the bottom part of the stem, and its length develops straight ahead on the current direction of the turtle. When your function terminates, the turtle should be back to the same position and the same orientation it started.

    Next, write the function fractalFlower that takes an integer representing the size of the flower, and an integer representing the maximum level (depth) of recursion, and again whatever else you need to do turtle graphics. Your function will paint a recursive fractal flower with the same basic shape outlined above, but with each petal recursively replaced by a scaled down version of the flower itself. For example, a fractal flower with a maximum recursion level of 4 will result in a picture like this one:

    Your program should include some test code that draws three flowers, all of them facing up, on three different positions of the screen: (a) a simple flower of size 200; (b) a fractal flower of depth 3 and size 250; and (c) a fractal flower of depth 4 and size 300. Your three test flowers should not overlap.

  5. fractalSun
    Using Python's Tkinter library, you will paint a majestic fractal sun. The fractal sun is composed of a circle of radius r, and 8 rays of length 2*r originating from the center of the circle and radially equally spaced. The external tip of each ray is also the origin of a recursively downsized fractal sun with radius equal to 1/4 of the radius of the sun at the previous level. Also, the suns originating at the tip of the rays will have different colors, i.e., the color of a sun is a function of the recursion level of the fractal sun itself. You can invent the coloring function you prefer, just make sure that your sun will look good no matter what the maximum level of recursion is going to be. Your fractal sun will be generated by a function fractalSun(canvas, xc, yc, r, level) which you will write. Your function will take as parameter a canvas to draw on, the (xc, yc) coordinates of the center of the sun, the radius r of the sun's circle, and an integer level representing the maximum depth of recursion of your fractal sun.

    The following picture shows a fractal sun with a maximum recursion depth of 5, with colors fading from yellow to red.

    Your program should include some test code that draws a fractal sun of depth 5 on a canvas of your choice.

  6. vicsekFractals
    Write the function vicsekFractals() that runs an animation that draws Vicsek fractals that fill a 400x400 window, where a Vicsek fractal is like so (where the depth is determined each time the user presses a digit key):


  7. pythagoreanTree
    First, read about the Pythagoras Tree here. With this in mind, write the function bonusPythagorasTree() that works like hFractal() only here it makes a Pythagoras Tree. For half-credit, just make the balanced one in the first row of the image. For full-credit, oscillate between sloped trees, as in the second image, but varying the angles back and forth over time so that there is a waving effect, as though blowing in the wind (well, with enough imagination).


  8. twoStackHanoi
    Background: Here, we will consider a modified form of the Towers of Hanoi problem. Given an input n>0, there are 2n discs of increasing size (instead of just n discs of increasing size). There are 3 poles as in the original problem (Pole 0, Pole 1, and Pole 2). We label the discs with numbers {1, 2, 3, ..., 2n}, where the label of a disc corresponds to its size. Initially, the odd-numbered discs (i.e. the discs {1, 3, 5, ..., 2n-1}) are on Pole 0, and the even-numbered discs (i.e. the discs {2, 4, 6, ..., 2n}) are on Pole 2. The goal is to get all the discs on Pole 1 (the middle pole).

    With this in mind, write the function twoStackHanoi(n) that takes a positive int n and returns a list of the moves in order required to solve a 2-stack Hanoi problem as described above (so, to ultimately move the n discs from Pole 0 and the n other discs from Pole 2 all to Pole 1, while also maintaining the Hanoi rule that no disc can be placed on a smaller disc). A "move" will be represented as a tuple (x,y), where x and y are both in the set {0,1,2}, and means to move one disc from Pole x to Pole y.

  9. getCourse(courseCatalog, courseNumber)
    Background: for this problem, we will use a so-called courseCatalog, which for this problem is a recursively-defined list-of-lists like so (this being just an example, your solution must work for any similarly-defined courseCatalog):
        courseCatalog = ["CMU",
                            ["CIT",
                                [ "ECE", "18-100", "18-202", "18-213" ],
                                [ "BME", "42-101", "42-201" ],
                            ],
                            ["SCS",
                                [ "CS",
                                  ["Intro", "15-110", "15-112" ],
                                  "15-122", "15-150", "15-213"
                                ],
                            ],
                            "99-307", "99-308"
                        ]
    
    Each level is defined by a list, and the first element of the list is the name of that level ("CMU", "CIT", "ECE", etc). The rest of the elements of a list contain either strings, which are course names offered at that level, or lists, which contain a sub-level. So we see that "99-307" is offered by "CMU", and "15-122" is offered by "CS". Also, the fully-qualified name of a course includes the dot-separated names of all the levels that contain it, in top-down order. Thus, the fully-qualified name of "15-112" is "CMU.SCS.CS.Intro.15-112", and the fully-qualified name of "99-307" is just "CMU.99-307". Finally, you may assume that a course name may not occur more than once in a course catalog.

    With this in mind, write the function getCourse(courseCatalog, courseNumber) that takes a courseCatalog, as defined above, and a courseNumber (as a string, such as "18-100" or "15-112"), and returns the fully-qualified name of the given course, or None if it is not in the catalog. For example, given the courseCatalog above, here are some test cases:
        assert(getCourse(courseCatalog, "18-100") == "CMU.CIT.ECE.18-100")
        assert(getCourse(courseCatalog, "15-112") == "CMU.SCS.CS.Intro.15-112")
        assert(getCourse(courseCatalog, "15-213") == "CMU.SCS.CS.15-213")
        assert(getCourse(courseCatalog, "99-307") == "CMU.99-307")
        assert(getCourse(courseCatalog, "15-251") == None)
    

  10. findRTP(digits) [20 pts]
    Background: A number n is a right-truncatable prime, or RTP, if every prefix of n (including n itself) are all prime. So, 593 is an RTP because 5, 59, and 593 are all prime. With this in mind, write the function findRTP(digits) that takes a positive int, digits, and returns the smallest RTP with that many digits, or None if no such number exists. To do this, you must use backtracking even if you could do it in some other way. At each step, try to add one more digit to the right of the number. Also, make sure you are only creating RTP's as you go. Note: even though findRTP(8) returns 23399339, it runs almost instantly because backtracking rules out most numbers without trying them, so it actually calls isPrime very few times.
    Hint: you may need to use callWithLargeStack so your isPrime does not stack overflow.

  11. pegSolitaire
    First, read up on peg solitaire here, and then read this entire writeup carefully. Then, write a peg solitaire solver using backtracking. The boards will be given to the function as a string of periods, O's, and spaces, which represent holes, pegs, and invalid spaces respectively (see examples below). The result should be returned as a list of moves to be made in the form (fromRow, fromCol, toRow, toCol), which indicates which piece to pick up (the from piece) and where it goes (the to piece). Your function must give the answer in a reasonable amount of time, which is defined as 10 seconds.

    Note that this problem is actually pretty difficult to do. This is because there are a lot of possible moves one can make at some stages in the puzzle (whereas in nQueens for example there are always only n possible moves). As such, we would grade on a rolling scale as such (examples of each case below).

    1pt boards with 10 pegs
    1pt boards with 14 pegs
    1pt boards with 16 pegs
    2pts boards with 32 pegs

    Your basic backtracking will eventually get the correct answer for all situations, but it'd probably take many many years to get the solution to the 32 peg board. As such, you will have to be a bit smarter to solve the higher peg boards, and maybe even need more advanced AI techniques to get the 32 peg board.

    Here are some boards to play with.
    board10 = """\ ... .O. ..OO.O. .O...O. ..O.O.. O.O ... """ board14 = """\ ... OO. ..O.OO. OO..OO. .OOO..O .O. ... """ board16 = """\ ... OO. ..OO... ..OO.OO OOO..OO OO. .O. """ board32 = """\ OOO OOO OOOOOOO OOO.OOO OOOOOOO OOO OOO """